/* NOTICE
    OpenJaWS - Open Java Weather Station
    
    Copyright (C) 2008 Grant Gardner <grant@lastweekend.com.au>
    
    OpenJaWS is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
NOTICE */
package au.com.lastweekend.ws2300;

import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Talks the WS2300 protocol over an RXTX SerialPort
 * 
 * @author Grant Gardner <grant@lastweekend.com.au>
 * 
 */
public class SerialDevice extends SafeDevice {


    static final Logger LOG = LoggerFactory.getLogger(SerialDevice.class);

    private static final int OPEN_TIMEOUT = 2000;
    private static final int RECEIVE_TIMEOUT = 500;
    private CommPort port;
    
    private static CommPort getSerialPort(String portName) throws IOException {

        portName = portName.trim();
        SerialPort port = null;

        try {
            CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
            port = (SerialPort) portIdentifier.open(SerialDevice.class.getName(), OPEN_TIMEOUT);
            port.setSerialPortParams(2400, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
            port.setDTR(false);
            port.setRTS(true);
            port.enableReceiveTimeout(RECEIVE_TIMEOUT);
            return port;

        } catch (Exception e) {
            if (port != null) {
                port.close();
            }
            if (LOG.isDebugEnabled()) {
                debugPorts();
            }
            
            throw newIOException("Unable to initialise port:" + portName + ":", e);
        }

    }


    @SuppressWarnings("unchecked")
    private static void debugPorts() {

        String rxtxProperty = System.getProperty("gnu.io.rxtx.SerialPorts");
        LOG.debug("gnu.io.rxtx.SerialPorts=" + rxtxProperty);
        Enumeration<CommPortIdentifier> ports = CommPortIdentifier.getPortIdentifiers();
        StringBuilder sb = new StringBuilder("Valid ports:");
        while (ports.hasMoreElements()) {
            CommPortIdentifier commPort = ports.nextElement();
            sb.append(commPort.getName());
            sb.append(';');
        }
        LOG.debug(sb.toString());
    }


    public SerialDevice(String portName) throws IOException {

        this(getSerialPort(portName));

    }

    private SerialDevice(CommPort port) throws IOException {

        super(port.getInputStream(), port.getOutputStream());
        this.port = port;

    }

    public void close() {

        if (port != null) {
            port.close();
            port = null;
        }
    }

    /**
     * Similar to open2300, read and write to the raw serial device.
     * 
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {

        SerialDevice device = null;
        String comPort = "";
        int address = 0;
        String command = "Z";
        String commandArg = "";

        if (args.length < 4) {
            // usage
            command = "0";
        } else {
            comPort = args[0];
            address = Integer.parseInt(args[1], 16);
            command = args[2];
            commandArg = args[3];

            device = new SerialDevice(comPort);

        }

        boolean setBit = false;

        try {
            switch (command.charAt(0)) {
                case 'w' :
                    // write
                    byte[] writeNibbles = new byte[commandArg.length()];
                    for (int i = 0; i < writeNibbles.length; i++) {
                        writeNibbles[i] = (byte) Integer.parseInt(commandArg.substring(i, i + 1), 16);
                    }
                    device.write(address, writeNibbles);
                    commandArg = Integer.toString((writeNibbles.length + 1) / 2);
                case 'r' :
                    // read
                    device.dump(new PrintWriter(System.out),address, Integer.parseInt(commandArg,16));
                    break;
                case 's' :
                    setBit = true;
                case 'u' :
                    device.writeBit(address, Integer.parseInt(commandArg), setBit);
                    break;
                default :
                    System.err.println("Usage:");
                    System.err.println("comport address r toAddress: Read date from address to toAddress");
                    System.err.println("comport address w hexString: Write hexString, to address, increasing left to right");
                    System.err.println("comport address s bitNumber: Set bitNumber at address");
                    System.err.println("comport address u bitNumber: Unset bitNumber at address");
                    }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (device != null) {
                device.close();
            }
        }
    }
}
